"use client"; import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import { redirect, useRouter } from "next/navigation"; import Link from "next/link"; import AuthenticatedLayout from "@/components/AuthenticatedLayout"; import { AppointmentStatusBadge } from "@/components/appointments/AppointmentStatusBadge"; import RecordsModal from "@/components/records/RecordsModal"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Separator } from "@/components/ui/separator"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { ApproveAppointmentModal } from "@/components/appointments/ApproveAppointmentModal"; import { Calendar, Clock, User, FileText, Video, CheckCircle2, XCircle, ArrowLeft, Loader2, AlertCircle, } from "lucide-react"; import { format } from "date-fns"; import { es } from "date-fns/locale"; import { notifications } from "@/lib/notifications"; import type { Appointment } from "@/types/appointments"; import type { Record } from "@/components/records/types"; import { canJoinMeeting, getAppointmentTimeStatus } from "@/utils/appointments"; interface PageProps { params: Promise<{ id: string }>; } export default function AppointmentDetailPage({ params }: PageProps) { const router = useRouter(); const { data: session, status } = useSession(); const [appointment, setAppointment] = useState(null); const [loading, setLoading] = useState(true); const [approveDialog, setApproveDialog] = useState(false); const [rejectDialog, setRejectDialog] = useState(false); const [motivoRechazo, setMotivoRechazo] = useState(""); const [actionLoading, setActionLoading] = useState(false); const [appointmentId, setAppointmentId] = useState(""); const [showRecordsModal, setShowRecordsModal] = useState(false); useEffect(() => { const loadParams = async () => { const resolvedParams = await params; setAppointmentId(resolvedParams.id); }; loadParams(); }, [params]); useEffect(() => { if (!appointmentId) return; const fetchAppointment = async () => { try { const response = await fetch(`/api/appointments/${appointmentId}`); if (!response.ok) { throw new Error("No se pudo cargar la cita"); } const data: Appointment = await response.json(); setAppointment(data); } catch (error) { notifications.appointments.loadError(); console.error(error); } finally { setLoading(false); } }; fetchAppointment(); }, [appointmentId]); if (status === "loading" || loading) { return (
); } if (!session) { redirect("/auth/login"); } if (!appointment) { return (

Cita no encontrada

La cita que buscas no existe o no tienes permisos para verla.

); } const userRole = session.user.role as "PATIENT" | "DOCTOR" | "ADMIN"; const isPatient = userRole === "PATIENT"; const isDoctor = userRole === "DOCTOR"; const otherUser = isPatient ? appointment.medico : appointment.paciente; const hasFecha = appointment.fechaSolicitada !== null; const fecha = hasFecha ? new Date(appointment.fechaSolicitada!) : null; const handleApprove = async (fechaSolicitada: Date, notas?: string) => { setActionLoading(true); try { const response = await fetch(`/api/appointments/${appointment.id}/approve`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ fechaSolicitada: fechaSolicitada.toISOString(), notas, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Error al aprobar la cita"); } const updated: Appointment = await response.json(); setAppointment(updated); setApproveDialog(false); notifications.appointments.approved(); } catch (error) { notifications.appointments.approveError(error instanceof Error ? error.message : undefined); console.error(error); } finally { setActionLoading(false); } }; const handleRejectConfirm = async () => { if (!motivoRechazo.trim()) return; setActionLoading(true); try { const response = await fetch(`/api/appointments/${appointment.id}/reject`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ motivoRechazo }), }); if (!response.ok) throw new Error("Error al rechazar la cita"); const updated: Appointment = await response.json(); setAppointment(updated); setRejectDialog(false); setMotivoRechazo(""); notifications.appointments.rejected(); } catch (error) { notifications.appointments.rejectError(); console.error(error); } finally { setActionLoading(false); } }; const handleCopyContent = (content: string) => { navigator.clipboard.writeText(content); notifications.records.copied(); }; const handleDownloadReport = (record: Record) => { const blob = new Blob([record.content], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `reporte-medico-${record.id.slice(-8)}-${new Date().toISOString().split("T")[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); notifications.records.downloaded(); }; const handleGeneratePDF = async (record: Record) => { // TODO: Implementar generación de PDF notifications.records.generated(); }; const handleCancel = async () => { setActionLoading(true); try { const response = await fetch(`/api/appointments/${appointment.id}`, { method: "DELETE", }); if (!response.ok) throw new Error("Error al cancelar la cita"); notifications.appointments.cancelled(); router.push("/appointments"); } catch (error) { notifications.appointments.cancelError(); console.error(error); setActionLoading(false); } }; const handleStartMeeting = async () => { setActionLoading(true); try { const response = await fetch(`/api/appointments/${appointment.id}/start-meeting`, { method: "POST", }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || error.error || "No se puede iniciar la videollamada"); } const data = await response.json(); // Redirigir a la sala de Jitsi router.push(`/appointments/${appointment.id}/meet`); } catch (error) { notifications.appointments.videocallError(error instanceof Error ? error.message : undefined); console.error(error); } finally { setActionLoading(false); } }; const handleComplete = async () => { setActionLoading(true); try { const response = await fetch(`/api/appointments/${appointment.id}/complete`, { method: "POST", }); if (!response.ok) throw new Error("Error al completar la cita"); const updated: Appointment = await response.json(); setAppointment(updated); notifications.appointments.completed(); } catch (error) { notifications.appointments.completeError(); console.error(error); } finally { setActionLoading(false); } }; return (
{/* Back Button */} {/* Header Card */}
{otherUser && ( {otherUser.name[0]}{otherUser.lastname[0]} )}
{otherUser ? `${otherUser.name} ${otherUser.lastname}` : isDoctor ? "Sin asignar" : "Médico por asignar"} {isPatient ? "Médico asignado" : "Paciente"}
{/* Details Card */} Detalles de la Cita {hasFecha && fecha ? (

Fecha

{format(fecha, "PPP", { locale: es })}

Hora

{format(fecha, "p", { locale: es })}

) : (

Fecha y hora

{appointment.estado === "PENDIENTE" ? "Pendiente de asignación por el médico" : "No asignada"}

)}

Motivo de consulta

{appointment.motivoConsulta}

{appointment.motivoRechazo && ( <>

Motivo de rechazo

{appointment.motivoRechazo}

)} {/* Solo mostrar sala si NO está completada */} {appointment.roomName && appointment.estado !== "COMPLETADA" && ( <>
)} {appointment.notasGuardadas && appointment.notasConsulta && ( <>

Notas de la Consulta

{appointment.notasGuardadasAt && (

Guardadas el {format(new Date(appointment.notasGuardadasAt), "d 'de' MMMM 'a las' HH:mm", { locale: es })}

)}
{appointment.notasConsulta}
)}
{/* Report Card - Si existe reporte asociado */} {appointment.record && ( Reporte Médico Asociado Reporte generado el {format( typeof appointment.record.createdAt === "string" ? new Date(appointment.record.createdAt) : appointment.record.createdAt, "d 'de' MMMM 'de' yyyy 'a las' HH:mm", { locale: es } )} )} {/* Actions Card */} Acciones
{isDoctor && appointment.estado === "PENDIENTE" && ( <> )} {isDoctor && appointment.estado === "APROBADA" && ( <> {canJoinMeeting(appointment.fechaSolicitada).canJoin ? ( ) : ( )} )} {isPatient && appointment.estado === "PENDIENTE" && ( )} {isPatient && appointment.estado === "APROBADA" && ( <> {canJoinMeeting(appointment.fechaSolicitada).canJoin ? ( ) : ( )} )} {/* Acciones para citas completadas */} {appointment.estado === "COMPLETADA" && ( <> {appointment.notasGuardadas && appointment.notasConsulta ? (

Consulta Finalizada

Las notas de la consulta están disponibles arriba

) : (

Consulta Finalizada

Esta cita ha sido completada

)} )} {/* Botón genérico de unirse - REMOVIDO: los botones específicos arriba cubren todos los casos necesarios */}
{/* Reject Dialog */} Rechazar Cita Por favor proporciona un motivo para rechazar esta cita. El paciente recibirá esta información.